package com.bobi.tfca.network.client;

import static android.app.job.JobInfo.PRIORITY_DEFAULT;
import static android.app.job.JobInfo.PRIORITY_MIN;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;

import com.bobi.tfca.R;
import com.bobi.tfca.model.Callback;
import com.bobi.tfca.model.LoginStatus;
import com.bobi.tfca.network.response.MessageResponse;
import com.bobi.tfca.model.MsgType;
import com.bobi.tfca.util.LogUtil;
import com.google.gson.Gson;

import java.net.InetSocketAddress;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.ReferenceCountUtil;

/**
 * 推送服务
 */ 
public class PushService extends Service {
    private static final String TAG = "PushService";
    private static final String HOST = "192.168.131.85";
    private static final int PORT = 8300;

    private SocketChannel socketChannel;
    private Callback<MessageResponse> receiveMsgCallback;
    private Callback<MessageResponse> notificationCallback;
    private Handler handler;

    @Override
    public void onCreate() {
        super.onCreate();
        handler = new Handler();
        AppCache.setService(this);
        startForeground();
    }

    /**
     * 启动前台服务
     */
    private void startForeground() {
        String channelId = null;
        // 8.0 以上需要特殊处理
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            channelId = createNotificationChannel("kim.hsl", "ForegroundService");
        } else {
            channelId = "";
        }
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId);
        Notification notification = builder.setOngoing(true)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setPriority(PRIORITY_DEFAULT)
                .setCategory(Notification.CATEGORY_SERVICE)
                .setContentTitle("萤火露营正在后台运行")
                .build();

        startForeground(1, notification);
    }

    /**
     * 创建通知通道
     * @param channelId
     * @param channelName
     * @return
     */
    @RequiresApi(Build.VERSION_CODES.O)
    private String createNotificationChannel(String channelId, String channelName){
        NotificationChannel chan = new NotificationChannel(channelId,
                channelName, NotificationManager.IMPORTANCE_NONE);
        chan.setLightColor(Color.BLUE);
        chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
        NotificationManager service = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        service.createNotificationChannel(chan);
        return channelId;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    public void setReceiveMsgCallback(Callback<MessageResponse> receiveMsgCallback) {
        this.receiveMsgCallback = receiveMsgCallback;
    }

    public void setNotificationCallback(Callback<MessageResponse> notificationCallback) {
        this.notificationCallback = notificationCallback;
    }

    private void connect(Callback<Void> callback) {
        if (AppCache.getStatus() == LoginStatus.CONNECTING) {
            return;
        }

        AppCache.updateStatus(LoginStatus.CONNECTING);
        NioEventLoopGroup group = new NioEventLoopGroup();
        new Bootstrap()
                .channel(NioSocketChannel.class)
                .group(group)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .option(ChannelOption.TCP_NODELAY, true)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        pipeline.addLast(new IdleStateHandler(0, 30, 0));
                        pipeline.addLast(new ObjectEncoder());
                        pipeline.addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null)));
                        pipeline.addLast(new ChannelHandle());
                    }
                })
                .connect(new InetSocketAddress(HOST, PORT))
                .addListener((ChannelFutureListener) future -> {
                    if (future.isSuccess()) {
                        socketChannel = (SocketChannel) future.channel();
                        callback.onEvent(200, "success", null);
                    } else {
                        Log.e(TAG, "connect failed");
                        close();
                        // 这里一定要关闭，不然一直重试会引发OOM
                        future.channel().close();
                        group.shutdownGracefully();
                        callback.onEvent(400, "connect failed", null);
                    }
                });
    }

    /**
     * 方案一：允许外部修改登录状态，这里只进行建立Socket连接操作，缺点不太规范
     * 方案二：在这里进行根据登录API返回的Response判断对应操作，并根据需要设置回调，缺点这里是java，其他是Kotlin，难得弄
     * @param callback
     */
    public void login(Callback<Void> callback) {
        if (AppCache.getStatus() == LoginStatus.CONNECTING) {
            return;
        }

        // 登录成功，建立Socket
        connect((code, msg, aVoid) -> {
            if (code == 200) {
                // 再次发送一个消息，将Id发过去
                MessageResponse messageResponse = new MessageResponse();
                messageResponse.setSenderId(AppCache.getMyInfo().getMemberId());
                messageResponse.setContentType(MsgType.LOGIN);
                socketChannel.writeAndFlush(messageResponse.toJson())
                        .addListener((ChannelFutureListener) future -> {
                            if (future.isSuccess()) {
                                LogUtil.INSTANCE.d("SOCKET连接成功！！！");
                                AppCache.updateStatus(LoginStatus.CONNECTED);
                            } else {
                                close();
                                LogUtil.INSTANCE.d("SOCKET发送LOGIN失败！！！");
                                AppCache.updateStatus(LoginStatus.UNCONNECTED);
                            }
                        });
            } else {
                close();
                AppCache.updateStatus(LoginStatus.UNCONNECTED);
                LogUtil.INSTANCE.d("SOCKET初次连接失败！！！");
                if (callback != null) {
                    handler.post(() -> callback.onEvent(400, "failed", null));
                }
            }
        });
    }

    public void sendMsg(MessageResponse messageResponse, Callback<Void> callback) {
        if (AppCache.getStatus() != LoginStatus.CONNECTED) {
            callback.onEvent(401, "unconnected", null);
            return;
        }

        socketChannel.writeAndFlush(messageResponse.toJson())
                .addListener((ChannelFutureListener) future -> {
                    if (callback == null) {
                        return;
                    }
                    if (future.isSuccess()) {
                        handler.post(() -> callback.onEvent(200, "success", null));
                    } else {
                        handler.post(() -> callback.onEvent(400, "failed", null));
                    }
                });
    }

    public void close() {
        if (socketChannel != null) {
            socketChannel.close();
            socketChannel = null;
        }
    }

    private class ChannelHandle extends SimpleChannelInboundHandler<String> {

        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            super.channelInactive(ctx);
            PushService.this.close();
            // 如果是登录状态才重连，否则不重连
            if (AppCache.checkLoginStatus()) {
                AppCache.updateStatus(LoginStatus.UNCONNECTED);
                retryLogin(3000);
            }
        }

        @Override
        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
            super.userEventTriggered(ctx, evt);
            if (evt instanceof IdleStateEvent) {
                IdleStateEvent e = (IdleStateEvent) evt;
                if (e.state() == IdleState.WRITER_IDLE) {
                    // 空闲了，发个心跳吧
                    MessageResponse messageResponse = new MessageResponse();
                    messageResponse.setSenderId(AppCache.getMyInfo().getMemberId());
                    messageResponse.setContentType(MsgType.PING);
                    ctx.writeAndFlush(messageResponse.toJson());
                }
            }
        }

        @Override
        protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
            Gson gson = new Gson();
            MessageResponse messageResponse = gson.fromJson(msg, MessageResponse.class);
            if (messageResponse.getContentType() == MsgType.PING) {
                Log.d(TAG, "receive ping from server");
            } else if (messageResponse.getContentType() == MsgType.TEXT) {
                if (receiveMsgCallback != null) {
                    Log.d(TAG, "receive text message " + messageResponse.getContent());
                    handler.post(() -> receiveMsgCallback.onEvent(200, "success", messageResponse));
                } else {
                    // 发个通知
                    if (notificationCallback != null) {
                        Log.d(TAG, "send notification " + messageResponse.getContent());
                        handler.post(() -> notificationCallback.onEvent(200, "success", messageResponse));
                    } else {
                        Log.d(TAG, "null notification ");
                    }
                }
            }

            ReferenceCountUtil.release(msg);
        }
    }

    private void retryLogin(long mills) {
        if (AppCache.getMyInfo() == null) {
            return;
        }
        handler.postDelayed(() -> login((code, msg, aVoid) -> {
            if (code != 200) {
                retryLogin(mills);
            }
        }), mills);
    }

//    @Override
//    public int onStartCommand(Intent intent, int flags, int startId) {
//        startForeground(1, new Notification());
//        return super.onStartCommand(intent, flags, startId);
//    }
}
